import ChessBoard from './ChessBoard';
import Robot from './Robot';

import { Chess, ChessHistory, GameState, UserChessListenner, GameOverListenner, GameResult, GoBangResult, ForbidRules  } from './Constants';

/**
 * 游戏控制器，游戏主逻辑实现
 */
class GameControl {
    /** 操作棋盘 HTMLCanvasElement 对象的对象，用于绘制棋盘、棋子、辅助线等功能 */
    chessBoard : ChessBoard;
    /**  HTMLCanvasElement 对象，绘制棋盘，承载棋盘的 HTML 对象 */
    chessDom : HTMLCanvasElement;
    /** 棋盘二维数组，记录棋盘落子点的实时状态 */
    grid : Chess[][];
    /** 对局的历史记录 */
    history : ChessHistory[];
    /** 棋盘单元格长度，单位 px */
    cellSize : number;
    /** 每行每列可以落子的数量（不是棋盘单元格数量），chessCountOnPerRowColumn 名称太长，用 perCount 代替 */
    perCount : number;
    /** 游戏状态 */
    gameState = GameState.END;
    /** 是否轮到用户落子 */
    userChess = false;
    /** 用户是否先手 */
    userFirst = false;
    /** 用户选择棋子类型 */
    userChessType = Chess.BLACK;
    /** 用户禁手的棋子类型 */
    userChessBanType = Chess.BAN_BLACK;
    /** 用户上一次落子的位置 */
    userLastChess = {x:-1, y:-1};
    /** 自动确认用户落子 */
    autoConfirm = true;
    /** 用户落子的事件的监听器 */
    private _userChessListenner : UserChessListenner = ()=>{};
    /** 游戏结束的事件的监听器 */
    private _gameOverListenner : GameOverListenner = ()=>{};
    /** 机器人对象 */
    robot : Robot;

    /**
     * 初始化构造函数
     * @param cellSize 棋盘单元格长度，单位 px 
     * @param chessCountOnPerRowColumn 每行每列可以落子的数量（不是棋盘单元格数量）
     * @param parent 棋盘所在父容器
     * @param autoConfirm 是否自动确认用户落子，如果是flase，则一定要调用 addUserChessListenner 方法注入落子监听器，然后手动调用 userComfirm 方法确认落子
     * @param userFirst 用户是否先手
     */
    constructor(cellSize:number, chessCountOnPerRowColumn:number, parent:HTMLElement){
        this.cellSize = cellSize;
        this.perCount = chessCountOnPerRowColumn;
        this.chessDom = document.createElement('canvas');
        this.chessDom.setAttribute("width", this.cellSize * this.perCount + 50 + "px");
        this.chessDom.setAttribute("height", this.cellSize * this.perCount + 50 + "px");
        if(parent.children.length>0){
            parent.children.item(0)?.before(this.chessDom);
        } else {
            parent.appendChild(this.chessDom);
        }
        this.chessBoard = new ChessBoard(this.cellSize, this.perCount, this.chessDom);
        this.history = [];
        this.grid = new Array(this.perCount);
        for (let x = 0; x < this.perCount; x++) {
            let cellRow : Chess[] = new Array(this.perCount);
            for (let y = 0; y < this.perCount; y++) {
                cellRow[y] = Chess.EMPTY;
            }
            this.grid[x] = cellRow;
        }
        this.bindEvent();
        this.robot = new Robot(this.chessBoard);
    }

    private bindEvent(){
        this.chessDom.onclick = (event)=>{
            if(this.gameState!==GameState.StART || !this.userChess) { // 游戏未开始不能落子；AI在落子中的时候，用户不能落子
                return;
            }
            // 边界检测，不允许在棋盘外落子
            const [x , y] = [this.chessBoard.getCoordinate(event.offsetX), this.chessBoard.getCoordinate(event.offsetY)];
            if(x>=0 && x<this.perCount && y>=0 && y<this.perCount){ 
                if(!this.grid[x][y].startsWith(Chess.EMPTY) || this.grid[x][y]===this.userChessBanType || this.grid[x][y]===Chess.BAN_ALL) {
                    return; // 已经落子的区域不能重复落子； 禁手不能落子
                }
                if(!this.autoConfirm){ // 重绘棋子，这样用户在还没有确认落子的时候，可以在任何地方重新落子
                    this.rePaint();
                }
                // 落子
                this.chessBoard.drawChess(x, y, this.userChessType);
                this.userLastChess.x = x;
                this.userLastChess.y = y;
                this._userChessListenner(x, y);
                if(this.autoConfirm){
                    this.userComfirm();
                }
            }
        }
    }

    /**
     * 开始游戏
     * @param type 用户所持棋子类型
     * @param userFirst 用户是否先手
     * @param autoConfirm 是否自动确认用户落子
     * @param forbidRules 禁手规则
     */
    gameStart(type:Chess, userFirst:boolean, autoConfirm:boolean, forbidRules : ForbidRules){
        this.clearPanel();
        this.gameState = GameState.StART;
        this.userFirst = userFirst;
        this.userChess = userFirst;
        this.userChessType = type;
        this.userChessBanType = type===Chess.WHITE?Chess.BAN_WHITE:Chess.BAN_BLACK;
        this.autoConfirm = autoConfirm;
        this.robot.changeConfig(this.userChessType===Chess.BLACK?Chess.WHITE:Chess.BLACK, !userFirst, forbidRules);
        //this.initData();
        if(!this.userFirst){
            this.robotChess();
        }
        console.log('开始游戏');
    }

    /**
     * 此方法仅用作开发测试
     */
    private initData(){
        for (let i = 5; i < 11; i++) {
            if(i===7) continue;
            this.grid[5][i] = Chess.BLACK;
            this.history.push({ x:5, y:i, type:Chess.BLACK});
        }

        for (let i = 3; i < 9; i++) {
            if(i===5) continue;
            this.grid[i][7] = Chess.WHITE;
            this.history.push({ x:i, y:7, type:Chess.WHITE});
        }
        this.robot.getChessPosition(this.grid, this.history.length);
        this.rePaint();
    }


    /**
     * 悔棋
     */
    gameRepentance(step:number){
        this.userChess = false;
        let his;
        for (let index = 0; index < step; index++) {
            his = this.history.pop();
            if(his){
                this.grid[his.x][his.y] = Chess.EMPTY;
            }
        }
        // 重新判断禁手
        this.robot.getChessPosition(this.grid, this.history.length);
        this.rePaint();
        console.log('用户悔棋');
        this.userChess = true;
    }

    /**
     * 重开棋局
     */
    reSet(){
        this.userChess = false;
        this.clearPanel();
        if(this.userFirst){
            this.userChess = true;
        } else {
            this.robotChess();
        }
        this.gameState = GameState.StART;
        console.log('重置棋局');
    }

    /** 清空棋盘 */
    private clearPanel(){
        if(this.history.length){
            this.history = [];
            for (let x = 0; x < this.perCount; x++) {
                for (let y = 0; y < this.perCount; y++) {
                    this.grid[x][y] = Chess.EMPTY;
                }
            }
            this.chessBoard.drawPanel();
            this.userLastChess.x = -1;
            this.userLastChess.y = -1;
        }
    }

    /**
     * 添加用户落子的事件监听（不需要确认落子）
     */
    addUserChessListenner(listenner:UserChessListenner){
        this._userChessListenner = listenner;
    }

    /**
     * 设置游戏结束的监听器
     */
    addGameOverListenner(listenner:GameOverListenner){
        this._gameOverListenner = listenner;
    }

    /**
     * 用户确认落子
     */
    userComfirm(){
        this.userChess = false;
        this.grid[this.userLastChess.x][this.userLastChess.y] = this.userChessType;
        this.history.push({ x:this.userLastChess.x, y:this.userLastChess.y, type:this.userChessType});
        this.userLastChess.x = -1;
        this.userLastChess.y = -1;
        if(this.history.length<9 || !this.checkWin(this.robot.robotFirst)){
            this.robotChess();
        }
    }

    /**
     * 机器人落子
     */
    robotChess(){
        const his = this.robot.getChessPosition(this.grid, this.history.length);
        this.grid[his.x][his.y] = his.type;
        this.history.push(his);
        this.chessBoard.drawChess(his.x, his.y, his.type);
        this.chessFlash(his.x, his.y, ()=>{
            if(this.history.length<9 || !this.checkWin(this.userFirst)){
                this.userChess = true;
            }
        });  // 机器人落子棋子闪烁效果如果不好看，可以考虑加一个颜色区别一下
    }

    /**
     * 判断输赢，以及平局
     * @param includeBan 是否需要判断禁手，如果下一个落子的选手是先手的话，就需要计算禁手了，除了禁手位置以外，没有位置可以落子的话，就是平局
     * @returns false 没有决出胜负，需要继续，true 则表示游戏已经分出胜负结果，不需要继续了
     */
    private checkWin(includeBan:boolean){
        if(this.history.length===this.perCount*this.perCount){
            // 这种判断方式在有禁手的情况下无法准确判断
            this.showResult({res:GameResult.EQULES, position:[]});
            return true;
        }
        let lineResult : GoBangResult | undefined;
        for (let x = 0; !lineResult && x < this.grid.length; x++) {
            for (let y = 0; !lineResult && y < this.grid[x].length; y++) {
                lineResult = this.findLine(x, y);
            }
        }
        if(!lineResult && includeBan){ // 判断平局
            let equles = true;
            for (let x = 0; equles && x < this.grid.length; x++) {
                for (let y = 0; equles && y < this.grid[x].length; y++) {
                    equles = this.grid[x][y]!==Chess.EMPTY;
                }
            }
            if(equles){
                lineResult = {res:GameResult.EQULES, position:[]};
            }
        }
        if(lineResult){
            this.showResult(lineResult);
            return true;
        }
        
        return false;
    }

    /**
     * 找到连成一条线的 5 个同色棋子
     * @param x 搜索点 x 坐标
     * @param y 搜索点 x 坐标
     * @returns 找到了的话返回 GoBangResult 对象，找不到则返回 undefined
     */
    private findLine(x:number, y:number) : GoBangResult | undefined{
        const lineArray = [
            [{x:x+1, y}, {x:x+2, y}, {x:x+3, y}, {x:x+4, y}],
            [{x:x+1, y:y+1}, {x:x+2, y:y+2}, {x:x+3, y:y+3}, {x:x+4, y:y+4}],
            [{x, y:y+1}, {x, y:y+2}, {x, y:y+3}, {x, y:y+4}],
            [{x:x-1, y:y+1}, {x:x-2, y:y+2}, {x:x-3, y:y+3}, {x:x-4, y:y+4}]
        ];
        let chessArr;
        for (let index = 0; index < lineArray.length; index++) {
            const value = lineArray[index];
            let last = value[3]
            if(last.x<this.grid.length && last.x>=0 && last.y<this.grid[x].length && last.y>=0
                    && !this.grid[x][y].startsWith(Chess.EMPTY) 
                    && this.grid[x][y]===this.grid[value[0].x][value[0].y]
                    && this.grid[value[0].x][value[0].y]===this.grid[value[1].x][value[1].y]
                    && this.grid[value[1].x][value[1].y]===this.grid[value[2].x][value[2].y]
                    && this.grid[value[2].x][value[2].y]===this.grid[value[3].x][value[3].y] ) {
                chessArr = value;
                break;
            }
        }
        if(chessArr) {
            const res = this.grid[x][y]===Chess.BLACK?GameResult.BLACK:GameResult.WHITE;
            return { res, position:[{x,y}, ...chessArr] };
        }
        return undefined;
    }

    /**
     * 根据历史记录，重绘棋局
     * @param hideX 重绘时需要隐藏掉的棋子 x 坐标
     * @param hideY 重绘时需要隐藏掉的棋子 y 坐标
     */
    private rePaint(hideX:number = -1, hideY:number = -1){
        let his;
        this.chessBoard.drawPanel();
        for (let index = 0; index < this.history.length; index++) {
            his = this.history[index];
            if(his.x===hideX && his.y===hideY)
                continue;
            this.chessBoard.drawChess(his.x, his.y, his.type);
        }
        
        // 把禁手位置也绘制出来
        for (let x = 0; x < this.grid.length; x++) {
            for (let y = 0; y < this.grid[x].length; y++) {
                if(this.grid[x][y].startsWith("empty-ban-")){
                    this.chessBoard.drawForbid(x, y, this.grid[x][y]);
                }
            }
        }
    }

    /**
     * 显示比赛结果，游戏结束
     * @param res 
     */
    private showResult(res:GoBangResult){
        this.userChess = false;     // 用户不能继续落子
        this.gameState = GameState.END; // 游戏结束
        if(res.res===GameResult.BLACK){
            if(this.userChessType===Chess.BLACK){
                alert('恭喜你获胜！');
            } else {
                alert('抱歉你输了！');
            }
        } else if(res.res===GameResult.WHITE){
            if(this.userChessType===Chess.WHITE){
                alert('恭喜你获胜！');
            } else {
                alert('抱歉你输了！');
            }
        } else {
            alert('难得打了个平局，真是破天荒啊！');
        }
        if(res.res!==GameResult.EQULES){
            this.lineFlash(res.position[0].x, res.position[0].y, res.position[4].x, res.position[4].y);
        }
        this._gameOverListenner({result:res.res, userChess:this.userChessType});
        console.log(res);
    }

    /**
     * 棋子闪烁
     * @param x 闪烁的棋子的 x 坐标
     * @param y 闪烁的棋子的 y 坐标
     * @param callback 棋子闪烁结束后的回调函数
     */
    private chessFlash(x:number, y:number, callback:Function){
        let times = 5;
        const timer = setInterval(()=>{
            if(this.gameState==GameState.StART){
                if(times%2===1){
                    this.rePaint(x, y);
                } else {
                    this.rePaint();
                }
                if(times>0){
                    times--;
                } else {
                    clearInterval(timer);
                    callback();
                }
            } else {
                clearInterval(timer);
            }
        }, 120);
    }

    /**
     * 辅助线闪烁
     * @param x1 起始 x 坐标
     * @param y1 起始 y 坐标
     * @param x2 终点 x 坐标
     * @param y2 终点 y 坐标
     */
    private lineFlash(x1:number, y1:number, x2:number, y2:number){
        let times = 6;
        const timer = setInterval(()=>{
            if(this.gameState==GameState.END){
                this.rePaint();
                if(times%2===0){
                    this.chessBoard.drawLine(x1, y1, x2, y2);
                }
                if(times>0){
                    times--;
                } else {
                    clearInterval(timer);
                }
            } else {
                clearInterval(timer);
                // 防止重新开始游戏后，这个辅助线还在显示，所以需要重绘一次
                if(times%2===1){
                    this.rePaint();
                }
            }
        }, 300);
    }
}


export default GameControl;